<template>
	<view v-if="inited" class="e-transition" ref="eTransition" @tap="clickHandler" :class="classes" :style="[mergeStyle]" @touchmove="(e) => noop(e, props.disabledTouchmove)">
		<slot />
	</view>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import mpMixin from '../../libs/mixin/mpMixin';
/**
 * transition  动画组件
 * @description
 * @tutorial
 * @property {String}			show			是否展示组件 （默认 false ）
 * @property {String}			mode			使用的动画模式 （默认 'fade' ）
 * @property {String | Number}	duration		动画的执行时间，单位ms （默认 '300' ）
 * @property {String}			timingFunction	使用的动画过渡函数 （默认 'ease-out' ）
 * @property {Object}			customStyle		自定义样式
 * @event {Function} before-enter	进入前触发
 * @event {Function} enter			进入中触发
 * @event {Function} after-enter	进入后触发
 * @event {Function} before-leave	离开前触发
 * @event {Function} leave			离开中触发
 * @event {Function} after-leave	离开后触发
 * @example
 */
export default defineComponent({
	name: 'e-transition',
	mixins: [mpMixin]
});
</script>

<script lang="ts" setup>
import { ref, watch, computed, nextTick, toRefs } from 'vue';
import { transitionProps, transitionEmits } from './props';
import { nextTick as _nextTick } from './transition';
import { sleep } from '../../libs/utils';
import { addStyle, noop } from '../../libs/utils/_utils';
import { useDefaultProps } from '../../libs/hooks';
import type { StyleProperties } from '../../libs/tokens';
import componentsProps, { type DefaultProps } from '../../libs/config/componentsProps';
// #ifndef APP-NVUE
import { getClassNames } from './transition';
// #endif
// #ifdef APP-NVUE
import { animation, getStyle } from './transition';
// #endif

const { transition: transitionDefaultProps } = toRefs(componentsProps);

const _props = defineProps(transitionProps);

const props = useDefaultProps<DefaultProps['transition']>(_props, transitionDefaultProps);
const emit = defineEmits(transitionEmits);

const inited = ref(false);
const eTransition = ref<any>(null);

const classes = ref('');
const viewStyle = ref<StyleProperties>({});
let status = '';
let transitionEnded = false;
let display = false;

const mergeStyle = computed(() => ({
	// #ifndef APP-NVUE
	transitionDuration: `${props.value.duration}ms`,
	// display: `${this.display ? '' : 'none'}`,
	transitionTimingFunction: props.value.timingFunction,
	// #endif
	// 避免自定义样式影响到动画属性，所以写在viewStyle前面
	...(addStyle(props.value.customStyle as Record<string, any>) as Record<string, any>),
	...viewStyle.value
}));

// 组件被点击发出事件
const clickHandler = () => {
	emit('click');
};
// #ifndef APP-NVUE
// vue版本的组件进场处理
const vueEnter = () => {
	// 动画进入时的类名
	const classNames = getClassNames(props.value.mode as string);
	// 定义状态和发出动画进入前事件
	status = 'enter';
	emit('beforeEnter');
	inited.value = true;
	display = true;
	classes.value = classNames.enter;
	nextTick(async () => {
		// #ifdef H5
		await sleep(20);
		// #endif
		// 标识动画尚未结束
		emit('enter');
		transitionEnded = false;
		// 组件动画进入后触发的事件
		emit('afterEnter');
		// 赋予组件enter-to类名
		classes.value = classNames['enter-to'];
	});
};
// 动画离场处理
const vueLeave = () => {
	// 如果不是展示状态，无需执行逻辑
	if (!display) return;
	const classNames = getClassNames(props.value.mode as string);

	// 标记离开状态和发出事件
	status = 'leave';
	emit('beforeLeave');
	// 获得类名
	classes.value = classNames.leave;

	nextTick(() => {
		// 动画正在离场的状态
		transitionEnded = false;
		emit('leave');
		// 组件执行动画，到了执行的执行时间后，执行一些额外处理
		setTimeout(onTransitionEnd, Number(props.value.duration));
		classes.value = classNames['leave-to'];
	});
};
// #endif
// #ifdef APP-NVUE
// nvue版本动画进场
const nvueEnter = () => {
	// 获得样式的名称
	const currentStyle = getStyle(props.value.mode as string);
	// 组件动画状态和发出事件
	status = 'enter';
	emit('beforeEnter');
	// 展示生成组件元素
	inited.value = true;
	display = true;
	// 在nvue安卓上，由于渲染速度慢，在弹窗，键盘，日历等组件中，渲染其中的内容需要时间
	// 导致出现弹窗卡顿，这里让其一开始为透明状态，等一定时间渲染完成后，再让其隐藏起来，再让其按正常逻辑出现
	viewStyle.value = {
		opacity: 0
	};
	// 等待弹窗内容渲染完成
	nextTick(() => {
		// 合并样式
		viewStyle.value = currentStyle.enter;
		Promise.resolve()
			.then(_nextTick)
			.then(() => {
				// 组件开始进入前的事件
				emit('enter');
				// nvue的transition动画模块需要通过ref调用组件，注意此处的ref不同于vue的this.$refs['e-transition']用法
				animation.transition(
					eTransition.value,
					{
						styles: currentStyle['enter-to'],
						duration: props.value.duration,
						timingFunction: props.value.timingFunction,
						needLayout: false,
						delay: 0
					},
					() => {
						// 动画执行完毕，发出事件
						viewStyle.value = currentStyle['enter-to'];
						_nextTick().then(() => {
							viewStyle.value = {}
						})
						emit('afterEnter');
					}
				);
			})
			.catch(() => {});
	});
};
const nvueLeave = () => {
	if (!display) {
		return;
	}
	const currentStyle = getStyle(props.value.mode as string);
	// 定义状态和事件
	status = 'leave';
	emit('beforeLeave');
	// 合并样式
	viewStyle.value = currentStyle.leave;
	// 放到promise中处理执行过程
	Promise.resolve()
		.then(_nextTick) // 等待几十ms
		.then(() => {
			transitionEnded = false;
			// 动画正在离场的状态
			emit('leave');
			animation.transition(
				eTransition.value,
				{
					styles: currentStyle['leave-to'],
					duration: props.value.duration,
					timingFunction: props.value.timingFunction,
					needLayout: false,
					delay: 0
				},
				() => {
					viewStyle.value = currentStyle['leave-to'];
					_nextTick().then(() => {
						viewStyle.value = {}
					})
					onTransitionEnd();
				}
			);
		})
		.catch(() => {});
};
// #endif
// 完成过渡后触发
const onTransitionEnd = () => {
	// 如果已经是结束的状态，无需再处理
	if (transitionEnded) return;
	transitionEnded = true;
	// 发出组件动画执行后的事件
	status === 'leave' ? emit('afterLeave') : emit('afterEnter');
	if (!props.value.show && display) {
		display = false;
		inited.value = false;
	}
};

watch(
	() => props.value.show,
	(newVal) => {
		// vue和nvue分别执行不同的方法
		// #ifdef APP-NVUE
		newVal ? nvueEnter() : nvueLeave();
		// #endif
		// #ifndef APP-NVUE
		newVal ? vueEnter() : vueLeave();
		// #endif
	},
	{
		immediate: true
	}
);
</script>

<style lang="scss" scoped>
@import '../../libs/theme/transition.scss';
</style>
